package com.cn.mvc.servlet;

import com.cn.mvc.annotation.*;
import com.cn.mvc.pojo.Handle;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class MyDispatcherServlet extends HttpServlet {

    private Properties properties = new Properties();
    // 缓存扫描到的文件路径
    private List<String> classNameCache = new ArrayList<>();

    // ioc 容器， 存放实例化的对象
    private Map<String, Object> ioc = new HashMap<>();

    private List<Handle> handleMapping = new ArrayList<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 1 加载配置文件
        // contextConfigLocal 为 初始化加载的资源文件
        String contextConfigLocal = config.getInitParameter("contextConfigLocal");
        doLoadConfig(contextConfigLocal);
        // 2 扫描相关的类， 扫描注解
        doScan(properties.getProperty("scanPackage"));
        // 3 初始化bean对象 （IOC容器，基于注解）
        doInstance();
        // 4 注入属性
        doAutowried();

        // 5 初始化相关组件
        doInitHandleMapping();
        System.out.println("MyDispatcherServlet 初始化完成");
    }

    private void doInitHandleMapping() {
        if (ioc.isEmpty()) return;

        for (Map.Entry<String, Object> entry: ioc.entrySet()) {
            Class<?> aClass = entry.getValue().getClass();
            // 不包含controller标签 ， 直接跳过
            if (!aClass.isAnnotationPresent(MyController.class)) continue;

            // 基础路径  /demo
            String baseUrl = "";
            if (aClass.isAnnotationPresent(MyRequestMapping.class)){
                MyRequestMapping annotation = aClass.getAnnotation(MyRequestMapping.class);
                baseUrl = annotation.value();
            }

            // 权限校验区
            Set<String> securitys = new HashSet<>();
            if (aClass.isAnnotationPresent(MySecurity.class)){
                MySecurity annotation = aClass.getAnnotation(MySecurity.class);
                securitys = Stream.of(annotation.value()).collect(Collectors.toSet());
            }

            // 所有方法
            Method[] methods = aClass.getMethods();
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];
                // 方法上是否有标签myrequestMapping
                if(!method.isAnnotationPresent(MyRequestMapping.class)) continue;

                MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
                String methodUrl = annotation.value();

                // 拼接全路径
                String url = baseUrl + methodUrl;
                // 创建handle对象， 剩下的就是处理参数位置信息
                Handle handle = new Handle(entry.getValue(), method, Pattern.compile(url));

                // 处理参数 位置信息
                Parameter[] parameters = method.getParameters();
                for (int j = 0; j < parameters.length; j++) {
                    Parameter parameter = parameters[j];

                    //getName(HttpServletRequest request, HttpServletResponse respm, String name)
                    if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class){
                        handle.getParam().put(parameter.getType().getSimpleName(), j);
                    }else {
                        handle.getParam().put(parameter.getName(), j);
                    }

                }

                // 如果方法上包含了 MySecurity 标签
                if(method.isAnnotationPresent(MySecurity.class)){
                    MySecurity mySecurity = method.getAnnotation(MySecurity.class);
                    String[] value = mySecurity.value();
                    securitys.addAll(Stream.of(value).collect(Collectors.toSet()));
                }
                if(!securitys.isEmpty()){
                    handle.setCheckSecurity(true);
                }
                handle.setSecurity(securitys);

                // 缓存路径--方法对照关系
                handleMapping.add(handle);
                //handleMapping.put(url, method);
            }

        }
    }

    private void doAutowried() {
        // 容器中 没有对象则返回
        if (ioc.isEmpty()) return;
        // 循环容器中的对象
        for (Map.Entry<String, Object> entry : ioc.entrySet()){
            System.out.println(entry.getKey() + ", " + entry.getValue());

            // 所有属性集合
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                if(fields[i].isAnnotationPresent(MyAutowired.class)){
                    MyAutowired myAutowired = fields[i].getAnnotation(MyAutowired.class);
                    String beanName = myAutowired.value();
                    if(beanName.isEmpty()){
                        beanName = fields[i].getType().getName();
                    }
                    // 开启暴力访问
                    fields[i].setAccessible(true);
                    try {
                        // 通过反射赋值
                        fields[i].set(entry.getValue(), ioc.get(beanName));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private void doInstance() {
        // 扫描到的所有对象都存放在fileNames中，
        //  取出所有，实例化。
        if (classNameCache.size() == 0) return;

        try {
            for (int i = 0; i < classNameCache.size(); i++) {
                String fileName = classNameCache.get(i);
                // 反射获取类
                Class<?> f = Class.forName(fileName);

                //  如果是 controller 层
                if (f.isAnnotationPresent(MyController.class)){
                    // 获取类型
                    String simpleName = f.getSimpleName();
                    String lowerFirstSimpleName = lowerFirst(simpleName);
                    ioc.put(lowerFirstSimpleName, f.newInstance());
                } else if(f.isAnnotationPresent(MyService.class)){
                    MyService myService = f.getAnnotation(MyService.class);
                    // 如果设置了beanName 则取设置的， 未设置则使用首字母小写
                    String beanName = myService.value();

                    if(!"".equals(beanName.trim())){
                        ioc.put(beanName, f.newInstance());
                    }else {
                        beanName = lowerFirst(f.getSimpleName());
                        ioc.put(beanName, f.newInstance());
                    }
                    // 将接口 注册到ioc容器中
                    Class<?>[] interfaces = f.getInterfaces();
                    for (int j = 0; j < interfaces.length; j++) {
                        Class<?> anInterface = interfaces[j];
                        ioc.put(anInterface.getName(), f.newInstance());
                    }
                }else{
                    continue;
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    // 首字母转小写
    private String lowerFirst(String name){
        char [] chars = name.toCharArray();
        if('A' <= chars[0] && chars[0] <= 'Z' ){
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }

    private void doScan(String scanPackage) {
        // 获取磁盘路径
        String cpath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        String path2 = scanPackage.replaceAll("\\.", "/");
        String scanPackagePath = cpath + path2;

        File pack = new File(scanPackagePath);
        // 获取子文件
        File[] files = pack.listFiles();

        for (File file : files){
            if(file.isDirectory()){
                doScan(scanPackage + "." + file.getName());
            }else {
                String className = scanPackage + "." + file.getName().replace(".class", "");
                classNameCache.add(className);
            }
        }


    }

    private void doLoadConfig(String contextConfigLocal) {
        // 将资源读取到流中
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocal);
        try {
            // 将流中的属性 加载到属性列表中
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 根据url 获取当前处理的handle
        Handle handle = getHandle(req);

        if (handle == null){
            resp.getWriter().write("404 not found");
            return;
        }
        if (!checkSecurity(handle, req)){
            System.out.println("没有权限");
            resp.getWriter().write("没有权限");
            return;
        }

        // 参数绑定
        // 获取参数类型 ， 主要获取参数的个数
        Class<?>[] parameterTypes = handle.getMethod().getParameterTypes();

        // 用于存放 参数
        Object[] objects = new Object[parameterTypes.length];

        // req中的所有参数
        Map<String, String[]> parameterMap = req.getParameterMap();
        // 便利paramterMap
        for(Map.Entry<String, String[]> map : parameterMap.entrySet()){
            // 将数组[1,2] 改为  1，2 的形式，去除[]
            String value = StringUtils.join(map.getValue(), ",");

            // 如果方法中的参数和形参匹配上，则填充数据
            if(!handle.getParam().containsKey(map.getKey())){
                continue;
            }
            // 普通参数参数位置
            Integer index = handle.getParam().get(map.getKey());
            objects[index] = value;
        }
        Integer requestIndex = handle.getParam().get(HttpServletRequest.class.getSimpleName());
        if(requestIndex != null){
            objects[requestIndex] = req;
        }

        Integer responeseIndex = handle.getParam().get(HttpServletResponse.class.getSimpleName());
        if(responeseIndex != null){
            objects[responeseIndex] = resp;
        }

        try {
            // 执行方法
            handle.getMethod().invoke(handle.getController(), objects);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    // 验证权限
    private boolean checkSecurity(Handle handle, HttpServletRequest req){

        if (!handle.isCheckSecurity()){
            return true;
        }

        String username = req.getParameter("username");
        if (username == null ){
            return false;
        }

        if (handle.getSecurity().contains(username)){
            return true;
        }

        return false;
    }


    private Handle getHandle(HttpServletRequest req) {
        String requestURI = req.getRequestURI();

        if(handleMapping == null){
            return null;
        }
        for(Handle handle : handleMapping){
            Matcher matcher = handle.getPattern().matcher(requestURI);
            if(!matcher.matches()){
                continue;
            }
            return handle;
        }
        return null;
    }


}
